﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace CharGen
{
    public partial class BuilderMain : Form
    {
        const int MAINSTAT_CAP = 20;
        const int POINTS_PER_LEVEL = 3;

        public Character SessionCharacter { get; set; }
        public Dictionary<string,string> SubStatMap { get; set; }
        public Dictionary<int,int> AdvancementTable { get; set; }
        public Dictionary <string, Archetype> Archetypes { get; set; }
        public bool IsClassOperation { get; set; }

        public int PointsSpentSession { get; set; }

        public bool Loading { get; set; }
        public BuilderMain()
        {
            PointsSpentSession = 0;
            Loading = true;
            InitializeComponent();
            SessionCharacter = new Character();

            SubStatMap = new Dictionary<string, string>();
            SubStatMap.Add("Power", "STR");
            SubStatMap.Add("Endurance", "STR");
            SubStatMap.Add("Vitality", "STR");
            SubStatMap.Add("Courage", "STR");

            SubStatMap.Add("Speechcraft", "CHA");
            SubStatMap.Add("Empathy", "CHA");
            SubStatMap.Add("Appearance", "CHA");
            SubStatMap.Add("Faith", "CHA");

            SubStatMap.Add("Mysticism", "INT");
            SubStatMap.Add("Education", "INT");
            SubStatMap.Add("Will", "INT");
            SubStatMap.Add("Ingenuity", "INT");

            SubStatMap.Add("Precision", "DEX");
            SubStatMap.Add("Acrobatics", "DEX");
            SubStatMap.Add("Deception", "DEX");
            SubStatMap.Add("Reflexes", "DEX");

            AdvancementTable = new Dictionary<int, int>(); //K: Exp, V: Level
            AdvancementTable.Add(0, 1);
            AdvancementTable.Add(1000, 2);
            AdvancementTable.Add(2250, 3);
            AdvancementTable.Add(3750, 4);
            AdvancementTable.Add(5500, 5);
            AdvancementTable.Add(7500, 6);
            AdvancementTable.Add(10000, 7);
            AdvancementTable.Add(13000, 8);
            AdvancementTable.Add(16500, 9);
            AdvancementTable.Add(20500, 10);
            AdvancementTable.Add(26000, 11);
            AdvancementTable.Add(32000, 12);
            AdvancementTable.Add(39000, 13);
            AdvancementTable.Add(47000, 14);
            AdvancementTable.Add(57000, 15);
            AdvancementTable.Add(69000, 16);
            AdvancementTable.Add(83000, 17);
            AdvancementTable.Add(99000, 18);
            AdvancementTable.Add(119000, 19);
            AdvancementTable.Add(143000, 20);
            AdvancementTable.Add(175000, 21);
            AdvancementTable.Add(210000, 22);
            AdvancementTable.Add(255000, 23);
            AdvancementTable.Add(310000, 24);
            AdvancementTable.Add(375000, 25);
            AdvancementTable.Add(450000, 26);
            AdvancementTable.Add(550000, 27);
            AdvancementTable.Add(675000, 28);
            AdvancementTable.Add(825000, 29);
            AdvancementTable.Add(1000000, 30);

            Archetypes = new Dictionary<string, Archetype>();
            var archDir = new DirectoryInfo(Path.GetDirectoryName(Application.ExecutablePath)+"\\Archetypes");
            var archFiles = archDir.GetFiles("*.bca");
            cbClass.Items.Clear();
            cbClass.Items.Add("-- Kein --");
            foreach (var archetypeFile in archFiles)
            {
                var at = Archetype.Load(archetypeFile.FullName);
                if (!Archetypes.ContainsKey(at.Name))
                {
                    Archetypes.Add(at.Name, at);
                    cbClass.Items.Add(at.Name);
                }
                else
                {
                    MessageBox.Show("Die Datei \""+ archetypeFile.Name+ "\" definiert den Archetyp \""+at.Name
                        +"\". Dieser Archetyp ist bereits definiert. Eintrag wird ignoriert.","Warnung",MessageBoxButtons.OK );
                }
            }

            ObjToUi();
            Loading = false;
        }

        private int GetPointCost(int abilityCurrent)
        {
            if (abilityCurrent <= 10)
                return 1;
            else if (abilityCurrent <= 15)
                return 2;
            else
                return 3;
        }

        private void btnNew_Click(object sender, EventArgs e)
        {
            SessionCharacter = new Character();
            charGrid.SelectedObject = SessionCharacter;
            charGrid.Update();
            PointsSpentSession = 0;
            lblAvailablePoints.Text = "0";
            ObjToUi();
        }

        private string ImageToBase64String(Image image)
        {
            if (image != null)
            {
                using (MemoryStream stream = new MemoryStream())
                {
                    image.Save(stream, image.RawFormat);
                    return Convert.ToBase64String(stream.ToArray());
                }
            }
            return "";
        }

        private Image ImageFromBase64String(string base64String)
        {
            if (base64String != null)
            {
                using (MemoryStream stream = new MemoryStream(
                    Convert.FromBase64String(base64String)))
                using (Image sourceImage = Image.FromStream(stream))
                {
                    return new Bitmap(sourceImage);
                }
            }
            return null;
        }

        private void btnSavetoXml_Click(object sender, EventArgs e)
        {
            var dr = DialogResult.Yes;
            if (lblAvailablePoints.ForeColor == Color.Red)
            {
                dr = MessageBox.Show("Dieser Charakter hat derzeit negative Stat-Punkte und ist ungültig. Fortfahren?", "Warnung", MessageBoxButtons.YesNo);
                if (dr == System.Windows.Forms.DialogResult.No)
                    return;
            }

            if (dr == DialogResult.Yes)
            {
                UiToObj();
                saveCharDialog.AddExtension = true;
                saveCharDialog.DefaultExt = ".bcb";
                saveCharDialog.Filter = "Beige Character Builder Dateien (*.bcb)|*.bcb|Alle Dateien (*.*)|*.*";
                saveCharDialog.FileName = SessionCharacter.Name;
                dr = saveCharDialog.ShowDialog();
                if (dr == DialogResult.OK)
                {
                    SessionCharacter.SaveToXml(saveCharDialog.FileName);
                }
            }
        }

        private void btnLoad_Click(object sender, EventArgs e)
        {
            openCharDialog.FileName = "";
            openCharDialog.Filter = "Beige Character Builder Dateien (*.bcb)|*.bcb|Alle Dateien (*.*)|*.*";
            openCharDialog.DefaultExt = ".bcb";
            var dr = openCharDialog.ShowDialog();
            if (dr == DialogResult.OK)
            {
                PointsSpentSession = 0;
                lblAvailablePoints.Text = "0";
                SessionCharacter = Character.LoadFromXml(openCharDialog.FileName);
                charGrid.SelectedObject = SessionCharacter;
                charGrid.Update();
                Loading = true;
                ObjToUi();
                Loading = false;
            }
        }

        private void ObjToUi()
        {
            edStrength.Text = SessionCharacter.Strength.ToString();
            edDexterity.Text = SessionCharacter.Dexterity.ToString();
            edCharisma.Text = SessionCharacter.Charisma.ToString();
            edIntelligence.Text = SessionCharacter.Intelligence.ToString();


            edName.Text = SessionCharacter.Name;
            edAge.Text = SessionCharacter.Age.ToString();
            edLevel.Text = SessionCharacter.Level.ToString();
            edEXP.Text = SessionCharacter.Experience.ToString();
            cbGender.SelectedIndex = (SessionCharacter.Sex ? 0 : 1);
            cbClass.SelectedIndex = cbClass.FindString(SessionCharacter.Class);

            PointsSpentSession = SessionCharacter.PointsSpent;
            lblAvailablePoints.Text = (Convert.ToInt32(lblAvailablePoints.Text) - PointsSpentSession).ToString();

            edPower.Text = SessionCharacter.Power.ToString();
            edCourage.Text = SessionCharacter.Courage.ToString();
            edEndurance.Text = SessionCharacter.Endurance.ToString();
            edVitality.Text = SessionCharacter.Vitality.ToString();

            edAcrobatics.Text = SessionCharacter.Acrobatics.ToString();
            edPrecision.Text = SessionCharacter.Precision.ToString();
            edReflexes.Text = SessionCharacter.Reflex.ToString();
            edDeception.Text = SessionCharacter.Deception.ToString();

            edMysticism.Text = SessionCharacter.Mysticism.ToString();
            edEducation.Text = SessionCharacter.Education.ToString();
            edIngenuity.Text = SessionCharacter.Ingenuity.ToString();
            edWill.Text = SessionCharacter.Will.ToString();

            edSpeechcraft.Text = SessionCharacter.Speechcraft.ToString();
            edAppearance.Text = SessionCharacter.Appearance.ToString();
            edEmpathy.Text = SessionCharacter.Empathy.ToString();
            edFaith.Text = SessionCharacter.Faith.ToString();

            pbCharPortrait.Image = ImageFromBase64String(SessionCharacter.Portrait);
        }

        

        private void UiToObj()
        {
            SessionCharacter.Strength = Convert.ToInt32(edStrength.Text);
            SessionCharacter.Dexterity = Convert.ToInt32(edDexterity.Text);
            SessionCharacter.Charisma = Convert.ToInt32(edCharisma.Text);
            SessionCharacter.Intelligence = Convert.ToInt32(edIntelligence.Text);

            SessionCharacter.Name = edName.Text;
            SessionCharacter.Age = Convert.ToInt32(edAge.Text);
            SessionCharacter.Level = Convert.ToInt32(edLevel.Text);
            SessionCharacter.Sex = (cbGender.SelectedIndex == 0);
            SessionCharacter.Experience = Convert.ToInt32( edEXP.Text );
            SessionCharacter.Class = cbClass.Text;
            SessionCharacter.PointsSpent = PointsSpentSession;

            SessionCharacter.Power = Convert.ToInt32(edPower.Text);
            SessionCharacter.Courage = Convert.ToInt32(edCourage.Text);
            SessionCharacter.Endurance = Convert.ToInt32(edEndurance.Text);
            SessionCharacter.Vitality = Convert.ToInt32(edVitality.Text);

            SessionCharacter.Acrobatics = Convert.ToInt32(edAcrobatics.Text);
            SessionCharacter.Precision = Convert.ToInt32(edPrecision.Text);
            SessionCharacter.Reflex = Convert.ToInt32(edReflexes.Text);
            SessionCharacter.Deception = Convert.ToInt32(edDeception.Text);

            SessionCharacter.Mysticism = Convert.ToInt32(edMysticism.Text);
            SessionCharacter.Education = Convert.ToInt32(edEducation.Text);
            SessionCharacter.Ingenuity = Convert.ToInt32(edIngenuity.Text);
            SessionCharacter.Will = Convert.ToInt32(edWill.Text);

            SessionCharacter.Speechcraft = Convert.ToInt32(edSpeechcraft.Text);
            SessionCharacter.Appearance = Convert.ToInt32(edAppearance.Text);
            SessionCharacter.Empathy = Convert.ToInt32(edEmpathy.Text);
            SessionCharacter.Faith = Convert.ToInt32(edFaith.Text);

            SessionCharacter.Portrait = ImageToBase64String(pbCharPortrait.Image);
        }


        private void subStat_Changed(object sender, EventArgs e)
        {
            if (!IsClassOperation)
            {
                var oldValue = Convert.ToInt32((sender as NumericUpDown).Text);
                var newValue = (sender as NumericUpDown).Value;

                if (oldValue == newValue)
                    return;

                var actionType = (newValue > oldValue);
                var points = Convert.ToInt32(lblAvailablePoints.Text);

                var enoughPoints = false;
                var parentStatOK = false;

                if (actionType) //Add scrore
                {
                    if ( points >= GetPointCost((int)newValue) )
                    {
                        enoughPoints = true;
                    }
                    else
                        enoughPoints = false;
                }
                else
                {
                    enoughPoints = true;
                    //TODO: CHECK AGAINST ARCHETYPE MINIMUMS
                }

                var statName = (sender as NumericUpDown).Name.Remove(0, 2);
                var parentStat = SubStatMap[statName];
                var parentStatValue = 0;
                NumericUpDown parentStatControl = null; 

                if (parentStat == "STR")
                    parentStatControl = edStrength;

                if (parentStat == "DEX")
                    parentStatControl = edDexterity;

                if (parentStat == "CHA")
                    parentStatControl = edCharisma;

                if (parentStat == "INT")
                    parentStatControl = edIntelligence;

                parentStatValue = (int)parentStatControl.Value;
                //Geht nicht, Parent-Stat zu low
                parentStatOK = (newValue <= parentStatValue);

                if (enoughPoints && parentStatOK)
                {
                    //point adjustment
                    if (actionType)
                    {
                        points-= GetPointCost((int)newValue);
                        PointsSpentSession += GetPointCost((int)newValue);
                    }
                    else
                    {
                        points += GetPointCost((int)newValue);
                        PointsSpentSession -= GetPointCost((int)newValue);
                    }

                    lblAvailablePoints.Text = points.ToString();
                    parentStatControl.Minimum = newValue;
                }
                else
                {
                    (sender as NumericUpDown).ValueChanged -= subStat_Changed;
                    (sender as NumericUpDown).Value = oldValue;
                    (sender as NumericUpDown).ValueChanged += subStat_Changed;
                }
            }
        }

        private void parentStat_Changed(object sender, EventArgs e)
        {
            if (!IsClassOperation)
            {
                var oldValue = Convert.ToInt32((sender as NumericUpDown).Text);
                var newValue = (sender as NumericUpDown).Value;

                if (oldValue == newValue)
                    return;

                var actionType = (newValue > oldValue);
                var points = Convert.ToInt32(lblAvailablePoints.Text);

                var enoughPoints = false;
                if (actionType) //Add scrore
                {
                    if (points > GetPointCost((int)newValue))
                    {
                        enoughPoints = true;
                    }
                    else
                        enoughPoints = false;
                }
                else
                {
                    enoughPoints = true;
                    //TODO: CHECK AGAINST ARCHETYPE MINIMUMS
                }

                if (enoughPoints)
                {
                    if (newValue <= MAINSTAT_CAP) //Hold cap for mainnstats
                    {
                        //point adjustment
                        if (actionType)
                        {
                            points -= GetPointCost((int)newValue);
                            PointsSpentSession += GetPointCost((int)newValue);
                        }
                        else
                        {
                            points += GetPointCost((int)oldValue);
                            PointsSpentSession -= GetPointCost((int)oldValue);
                        }

                        lblAvailablePoints.Text = points.ToString();
                    }
                    else
                    {
                        (sender as NumericUpDown).ValueChanged -= parentStat_Changed;
                        (sender as NumericUpDown).Value = oldValue;
                        (sender as NumericUpDown).ValueChanged += parentStat_Changed;
                    }
                }
                else
                {
                    (sender as NumericUpDown).ValueChanged -= parentStat_Changed;
                    (sender as NumericUpDown).Value = oldValue;
                    (sender as NumericUpDown).ValueChanged += parentStat_Changed;
                }
            }
        }

        private void beendenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void überToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            var abb = new BuilderAbout();
            abb.ShowDialog();
        }

        private void edEXP_ValueChanged(object sender, EventArgs e)
        {
            var level = 1;
            var value = ((NumericUpDown)sender).Value;
            for (int i = 0; i < AdvancementTable.Count; i++)
            {
                if (i + 1 == AdvancementTable.Count)
                {
                    level = 30;
                }
                else if ((value >= AdvancementTable.ElementAt(i).Key) && (value < AdvancementTable.ElementAt(i + 1).Key))
                {
                    level = AdvancementTable.ElementAt(i).Value;
                    break;
                }
            }

            edLevel.Text = level.ToString();
        }

        private void btnToNextLevel_Click(object sender, EventArgs e)
        {
            var target = Convert.ToInt32(edLevel.Text)+1;
            if (target <= AdvancementTable.Last().Value)
            {
                var exp = from kvp in AdvancementTable where kvp.Value == target select kvp.Key;
                edEXP.Text = exp.First().ToString();
            }
        }

        private void btnPreviousLevel_Click(object sender, EventArgs e)
        {
            var target = Convert.ToInt32(edLevel.Text) - 1;
            if (target >= AdvancementTable.First().Value)
            {
                var exp = from kvp in AdvancementTable where kvp.Value == target select kvp.Key;
                edEXP.Text = exp.First().ToString();
            }
        }

        private void cbClass_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (!Loading)
            {
                var dr = MessageBox.Show("Ändern der Klasse setzt alle Stat-Werte und Punkte zurück. Fortfahren?",
                    "Warnung", MessageBoxButtons.YesNo);

                if (dr == DialogResult.Yes)
                {
                    IsClassOperation = true;
                    if (cbClass.SelectedIndex > 0)
                    {
                        var classToApply = Archetypes[cbClass.Text];
                        ApplyClassToUi(classToApply);
                        CalculatePointsReset();
                        edArchDescription.Text = classToApply.Description.Replace("\\n",Environment.NewLine).Trim();
                    }
                    else
                    {
                        ApplyClassToUi(new Archetype());
                        edArchDescription.Text = "";
                    }
                    IsClassOperation = false;
                }
            }
        }

        private void CalculatePointsReset()
        { 
            var amount =0;
            var level = Convert.ToDouble(edLevel.Text);
            amount = (int)((level-1) * 3); //0 Punkte auf level 1
            lblAvailablePoints.Text = amount.ToString();
            PointsSpentSession = 0;
        }

        private void ApplyClassToUi(Archetype at)
        {
            edStrength.Minimum = at.Strength;
            edDexterity.Minimum = at.Dexterity;
            edCharisma.Minimum = at.Charisma;
            edIntelligence.Minimum = at.Intelligence;

            edPower.Minimum = at.Power;
            edCourage.Minimum = at.Courage;
            edEndurance.Minimum = at.Endurance;
            edVitality.Minimum = at.Vitality;

            edAcrobatics.Minimum = at.Acrobatics;
            edPrecision.Minimum = at.Precision;
            edReflexes.Minimum = at.Reflex;
            edDeception.Minimum = at.Deception;

            edMysticism.Minimum = at.Mysticism;
            edEducation.Minimum = at.Education;
            edIngenuity.Minimum = at.Ingenuity;
            edWill.Minimum = at.Will;

            edSpeechcraft.Minimum = at.Speechcraft;
            edAppearance.Minimum = at.Appearance;
            edEmpathy.Minimum = at.Empathy;
            edFaith.Minimum = at.Faith;

            //----
            edStrength.Text = at.Strength.ToString();
            edDexterity.Text = at.Dexterity.ToString();
            edCharisma.Text = at.Charisma.ToString();
            edIntelligence.Text = at.Intelligence.ToString();

            edPower.Text = at.Power.ToString();
            edCourage.Text = at.Courage.ToString();
            edEndurance.Text = at.Endurance.ToString();
            edVitality.Text = at.Vitality.ToString();

            edAcrobatics.Text = at.Acrobatics.ToString();
            edPrecision.Text = at.Precision.ToString();
            edReflexes.Text = at.Reflex.ToString();
            edDeception.Text = at.Deception.ToString();

            edMysticism.Text = at.Mysticism.ToString();
            edEducation.Text = at.Education.ToString();
            edIngenuity.Text = at.Ingenuity.ToString();
            edWill.Text = at.Will.ToString();

            edSpeechcraft.Text = at.Speechcraft.ToString();
            edAppearance.Text = at.Appearance.ToString();
            edEmpathy.Text = at.Empathy.ToString();
            edFaith.Text = at.Faith.ToString();
        }

        private void edLevel_TextChanged(object sender, EventArgs e)
        {
            var target = Convert.ToInt32(edLevel.Text);
            CalculatePointsReset();
            lblAvailablePoints.Text = (Convert.ToInt32(lblAvailablePoints.Text)- PointsSpentSession).ToString();
        }

        private void edEXP_KeyDown(object sender, KeyEventArgs e)
        {
            foreach (Control ctrl in panMain.Controls)
            {
                if (ctrl.Enabled)
                {
                    if ((ctrl != (Control)sender) && ctrl.Name != "btnOK")
                        ctrl.Enabled = false;
                }
            }
            panStr.Enabled = false;
            panCha.Enabled = false;
            panDex.Enabled = false;
            panInt.Enabled = false;
        }

        private void edEXP_Leave(object sender, EventArgs e)
        {
            foreach (Control ctrl in panMain.Controls)
            {
                if (!ctrl.Enabled)
                    ctrl.Enabled = true;
            }
            panStr.Enabled = true;
            panCha.Enabled = true;
            panDex.Enabled = true;
            panInt.Enabled = true;
        }

        private void lblAvailablePoints_TextChanged(object sender, EventArgs e)
        {
            var points = Convert.ToInt32(lblAvailablePoints.Text);
            if (points < 0)
                lblAvailablePoints.ForeColor = Color.Red;
            else
                lblAvailablePoints.ForeColor = Color.Black;
        }

        private void edName_TextChanged(object sender, EventArgs e)
        {
            lblDescTitle.Text = "Über " + edName.Text + ": ";
        }

        private void btnChoosePortrait_Click(object sender, EventArgs e)
        {
            openCharDialog.FileName = "";
            openCharDialog.Filter = "Bilder|*.png;*.jpg;*.jpeg;*.bmp|Alle Dateien (*.*)|*.*";
            var dr = openCharDialog.ShowDialog();
            if (dr == DialogResult.OK)
            {
                pbCharPortrait.Image = new Bitmap(openCharDialog.FileName);
            }
        }
    }
}
